package com.yupi.yuaicodemother.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import com.yupi.yuaicodemother.ai.AiCodeGenTypeRoutingService;
import com.yupi.yuaicodemother.ai.AiCodeGenTypeRoutingServiceFactory;
import com.yupi.yuaicodemother.common.DeleteRequest;
import com.yupi.yuaicodemother.constant.AppConstant;
import com.yupi.yuaicodemother.constant.UserConstant;
import com.yupi.yuaicodemother.core.AiCodeGeneratorFacade;
import com.yupi.yuaicodemother.core.builder.VueProjectBuilder;
import com.yupi.yuaicodemother.core.handler.StreamHandlerExecutor;
import com.yupi.yuaicodemother.exception.BusinessException;
import com.yupi.yuaicodemother.exception.ErrorCode;
import com.yupi.yuaicodemother.exception.ThrowUtils;
import com.yupi.yuaicodemother.langGraph4j.CodeGenConcurrentWorkflow;
import com.yupi.yuaicodemother.mapper.AppMapper;
import com.yupi.yuaicodemother.model.dto.app.AppAddRequest;
import com.yupi.yuaicodemother.model.dto.app.AppQueryRequest;
import com.yupi.yuaicodemother.model.dto.app.AppUpdateRequest;
import com.yupi.yuaicodemother.model.entity.App;
import com.yupi.yuaicodemother.model.entity.User;
import com.yupi.yuaicodemother.model.enums.ChatHistoryMessageTypeEnum;
import com.yupi.yuaicodemother.model.enums.CodeGenTypeEnum;
import com.yupi.yuaicodemother.model.vo.AppVO;
import com.yupi.yuaicodemother.model.vo.UserVO;
import com.yupi.yuaicodemother.service.AppService;
import com.yupi.yuaicodemother.service.ChatHistoryService;
import com.yupi.yuaicodemother.service.ScreenshotService;
import com.yupi.yuaicodemother.service.UserService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;

import java.io.File;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 应用 服务层实现。
 *
 */
@Service
@Slf4j
public class AppServiceImpl extends ServiceImpl<AppMapper, App>  implements AppService{

    @Resource
    private UserService userService;

    @Resource
    private AiCodeGeneratorFacade aiCodeGeneratorFacade;

    @Resource
    private ChatHistoryService chatHistoryService;

    @Resource
    private StreamHandlerExecutor streamHandlerExecutor;

    @Resource
    private VueProjectBuilder vueProjectBuilder;

    @Resource
    private ScreenshotService screenshotService;

    @Resource
    private CodeGenConcurrentWorkflow codeGenConcurrentWorkflow;

    @Resource
    private AiCodeGenTypeRoutingServiceFactory aiCodeGenTypeRoutingServiceFactory;

    @Value("${code.deploy-host:http://localhost}")
    private String deployHost;

    @Override
    public Long crateApp(AppAddRequest appAddRequest, User loginUser) {

        // 参数校验
        ThrowUtils.throwIf(appAddRequest == null, ErrorCode.PARAMS_ERROR);

        // 获取信息
        String initPrompt = appAddRequest.getInitPrompt();

        // 初始提示词不能为空
        if(StrUtil.isBlank(initPrompt)){
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "初始提示词不能为空！");
        }

        // 构造入库对象
        App app = new App();
        BeanUtils.copyProperties(appAddRequest, app);
        app.setUserId(loginUser.getId());
        // 以初始提示词前 12 位作为应用名
        app.setAppName(initPrompt.substring(0, Math.min(initPrompt.length(), 12)));

        // TODO 临时制定创建的 app 都是 vue 类型
        // app.setCodeGenType(CodeGenTypeEnum.VUE_PROJECT.getValue());

        // 使用智能路由工厂, 创建 AI Service bean
        AiCodeGenTypeRoutingService routingService =
                aiCodeGenTypeRoutingServiceFactory.createAiCodeGenTypeRoutingService();

        // 通过 bean , 获取原始提示词的生成类型
        CodeGenTypeEnum codeGenTypeEnum = routingService.routeCodeGenType(initPrompt);
        app.setCodeGenType(codeGenTypeEnum.getValue());

        // 插入数据库
        boolean result = this.save(app);
        if(!result){
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "保存应用失败！");
        }

        return app.getId();
    }

    /**
     * 删除应用时关联删除对话历史, 重写内置方法
     *
     * @param id 应用ID
     * @return 是否成功
     */
    @Override
    public boolean removeById(Serializable id) {
        // 校验 id, 为空返回 false, 删除失败
        if (id == null) {
            return false;
        }

        // 将 id 转为 Long
        Long appId = Long.valueOf(id.toString());

        if (appId <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }

        // 删除关联对话失败, 捕获并处理异常
        try {
            // 删除 appId 对应的对话历史
            chatHistoryService.deleteByAppId(appId);
        } catch (Exception e) {
            log.error("删除应用关联对话历史失败: {}", e.getMessage());
        }

        return super.removeById(id);
    }

    @Override
    public void updateApp(AppUpdateRequest appUpdateRequest, User loginUser) {
        // 校验参数
        if(appUpdateRequest == null || appUpdateRequest.getId() == null){
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }

        // 查看应用是否存在
        Long appId = appUpdateRequest.getId();
        if(appId <= 0){
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        App oldApp = this.getById(appId);
        if(oldApp == null){
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "应用不存在！");
        }

        // 仅本人可更新

        if (!oldApp.getUserId().equals(loginUser.getId())){
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "无权限修改当前应用！");
        }

        // 构造入库数据
        App app = new App();
        app.setId(appId);
        app.setAppName(appUpdateRequest.getAppName());
        // 设置编辑时间
        app.setEditTime(LocalDateTime.now());
        boolean result = this.updateById(app);
        if(!result){
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "操作失败！");
        }
    }

    @Override
    public void deleteApp(DeleteRequest deleteRequest, User loginUser) {
        // 校验参数
        if(deleteRequest == null || deleteRequest.getId() == null){
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
        }

        Long appId = deleteRequest.getId();
        if(appId <= 0){
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
        }

        App oldApp = this.getById(appId);

        // 校验当前应用是否存在
        if(oldApp == null){
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "当前应用不存在！");
        }

        // 仅本人和管理员可以删除
        if(!(oldApp.getUserId() != loginUser.getId() || loginUser.getUserRole().equals(UserConstant.ADMIN_ROLE))){
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "您无权限删除当前应用！");
        }

        boolean result = this.removeById(oldApp);
        if(!result){
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "删除失败！");
        }
    }

    @Override
    public AppVO getAppVO(App app) {
        if(app == null){
            return null;
        }

        // 脱敏 app 信息
        AppVO appVO = new AppVO();
        BeanUtils.copyProperties(app, appVO);

        // 脱敏用户信息
        Long userId = app.getUserId();
        if(userId != null){
            User user = userService.getById(userId);
            UserVO userVO = userService.getUserVO(user);
            // 应用和用户关联
            appVO.setUser(userVO);
        }

        return appVO;
    }

    @Override
    public QueryWrapper getQueryWrapper(AppQueryRequest appQueryRequest) {
        if (appQueryRequest == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "请求参数为空");
        }
        Long id = appQueryRequest.getId();
        String appName = appQueryRequest.getAppName();
        String cover = appQueryRequest.getCover();
        String initPrompt = appQueryRequest.getInitPrompt();
        String codeGenType = appQueryRequest.getCodeGenType();
        String deployKey = appQueryRequest.getDeployKey();
        Integer priority = appQueryRequest.getPriority();
        Long userId = appQueryRequest.getUserId();
        String sortField = appQueryRequest.getSortField();
        String sortOrder = appQueryRequest.getSortOrder();
        return QueryWrapper.create()
                .eq("id", id)
                .like("appName", appName)
                .like("cover", cover)
                .like("initPrompt", initPrompt)
                .eq("codeGenType", codeGenType)
                .eq("deployKey", deployKey)
                .eq("priority", priority)
                .eq("userId", userId)
                .orderBy(sortField, "ascend".equals(sortOrder));
    }

    @Override
    public List<AppVO> getAppVOList(List<App> appList) {
        // 参数列表为空, 返回空列表
        if(CollUtil.isEmpty(appList)){
            return new ArrayList<>();
        }

        // 批量获取用户信息，避免 N+1 查询问题
        // 收集 app 列表的 userId
        Set<Long> userIdSet = appList.stream()
                .map(App::getUserId)
                .collect(Collectors.toSet());

        // 根据 userId 收集用户信息
        Map<Long, UserVO> userVOMap = userService.listByIds(userIdSet)
                .stream()
                // userId 映射 userVO
                .collect(Collectors.toMap(User::getId, userService::getUserVO));

        // 一次性组装所有 AppVO，根据 userId 从 Map 中取到需要的用户信息
        List<AppVO> appVOList = appList.stream().map(app -> {
            // 脱敏每一个 app 元素
            AppVO appVO = getAppVO(app);
            UserVO userVO = userVOMap.get(app.getUserId());
            appVO.setUser(userVO);
            return appVO;
        }).collect(Collectors.toList());

        return appVOList;
    }

    @Override
    public Flux<String> chatToGenCode(Long appId, String message, User loginUser) {
        // 参数校验
        if(appId == null || appId < 0){
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "appId 参数错误");
        }

        if(StrUtil.isBlank(message)){
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "提示词不能为空！");
        }

        // 查询应用信息
        App app = this.getById(appId);
        if(app == null){
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "应用不存在，无法对话！");
        }

        // 仅本人可对该应用进行对话
        if(!app.getUserId().equals(loginUser.getId())){
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "您无权限与当前应用对话哦！");
        }

        // 获取应用生成类型
        String codeGenType = app.getCodeGenType();
        CodeGenTypeEnum codeGenTypeEnum = CodeGenTypeEnum.getEnumByValue(codeGenType);
        if(codeGenTypeEnum == null){
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "不支持的应用生成类型！");
        }

        // 生成代码前, 保存消息到 chatHistory 表中
        chatHistoryService.addChatMessage(appId, message,
                ChatHistoryMessageTypeEnum.USER.getValue(), loginUser.getId());

        // 对于网站生成类型（HTML、MULTI_FILE、VUE_PROJECT），先执行工作流进行图片素材收集和提示词增强
        String enhancedMessage = message;
//        if (codeGenTypeEnum == CodeGenTypeEnum.HTML ||
//            codeGenTypeEnum == CodeGenTypeEnum.MULTI_FILE ||
//            codeGenTypeEnum == CodeGenTypeEnum.VUE_PROJECT) {
//            try {
//                log.info("开始执行工作流进行图片素材收集和提示词增强，appId: {}, 类型: {}", appId, codeGenTypeEnum);
//                enhancedMessage = codeGenConcurrentWorkflow.executeWorkflowToPromptEnhancement(
//                        message, appId, codeGenTypeEnum);
//                log.info("工作流执行完成，使用增强后的提示词，原始长度: {}, 增强后长度: {}",
//                        message.length(), enhancedMessage.length());
//            } catch (Exception e) {
//                log.error("执行工作流失败，使用原始提示词: {}", e.getMessage(), e);
//                // 工作流执行失败时，使用原始提示词，不影响原有功能
//                enhancedMessage = message;
//            }
//        }
        // 生成并保存代码（使用增强后的提示词）
        Flux<String> contentFlux = aiCodeGeneratorFacade.generateAndSaveCodeStream(enhancedMessage, codeGenTypeEnum, appId);

        // 调用流执行器, 根据类型解析和拼接流
        return streamHandlerExecutor.doExecute(contentFlux, chatHistoryService, appId, loginUser, codeGenTypeEnum);
    }

    @Override
    public String deployApp(Long appId, User loginUser) {
        if(appId == null || appId < 0){
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
        }

        if(loginUser == null){
            throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR, "用户未登录");
        }

        App app = this.getById(appId);
        if(app == null){
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "应用不存在！");
        }

        if(!app.getUserId().equals(loginUser.getId())){
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "您无权限部署该应用！");
        }

        // 检查是否已有 deployKey
        String deployKey = app.getDeployKey();

        // 没有则生成 6 位 deployKey（大小写字母 + 数字）
        if(StrUtil.isBlank(deployKey)){
            deployKey = RandomUtil.randomString(6);
        }


        // 获取代码生成类型，构建源目录路径 code_ouyput/codeGenType_appId
        String codeGenType = app.getCodeGenType();
        String sourceDirName = codeGenType + "_" + appId;
        String sourceDirPath = AppConstant.CODE_OUTPUT_ROOT_DIR + File.separator +  sourceDirName;

        // 检查源目录是否存在
        File sourceDir = new File(sourceDirPath);
        if (!sourceDir.exists() || !sourceDir.isDirectory()) {
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "应用代码不存在，请先生成代码");
        }

        // Vue 项目特殊处理：执行构建
        CodeGenTypeEnum codeGenTypeEnum = CodeGenTypeEnum.getEnumByValue(codeGenType);
        if (codeGenTypeEnum == CodeGenTypeEnum.VUE_PROJECT) {
            // Vue 项目需要构建
            boolean buildSuccess = vueProjectBuilder.buildProject(sourceDirPath);
            ThrowUtils.throwIf(!buildSuccess, ErrorCode.SYSTEM_ERROR, "Vue 项目构建失败，请检查代码和依赖");
            // 检查 dist 目录是否存在
            File distDir = new File(sourceDirPath, "dist");
            ThrowUtils.throwIf(!distDir.exists(), ErrorCode.SYSTEM_ERROR, "Vue 项目构建完成但未生成 dist 目录");
            // 将 dist 目录作为部署源
            sourceDir = distDir;
            log.info("Vue 项目构建成功，将部署 dist 目录: {}", distDir.getAbsolutePath());
        }

        // 复制文件到部署目录
        String deployDirPath = AppConstant.CODE_DEPLOY_ROOT_DIR + File.separator + deployKey;

        try {
            FileUtil.copyContent(sourceDir, new File(deployDirPath), true);
        } catch (IORuntimeException e) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "部署失败 : " + e.getMessage());
        }

        // 更新应用的 deployKey 和部署时间
        App updateApp = new App();
        // 持久层（MyBatis-Flex 框架）：在执行 update 操作时，检测到实体主键 id 为 null，触发框架级异常
        updateApp.setId(appId);
        updateApp.setDeployKey(deployKey);
        updateApp.setUpdateTime(LocalDateTime.now());
        boolean result = this.updateById(updateApp);
        if(!result){
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "部署失败！");
        }

        // 构造应用可访问的 URL
        // String appDeployUrl = String.format("%s/%s", AppConstant.CODE_DEPLOY_HOST, deployKey);
        String appDeployUrl = String.format("%s/%s/", deployHost, deployKey);

        // 异步生成截图并更新应用封面
        generateAppScreenshotAsync(appId, appDeployUrl);

        return appDeployUrl;
    }

    /**
     * 异步生成应用截图并更新封面
     *
     * @param appId  应用ID
     * @param appUrl 应用访问URL
     */
    @Override
    public void generateAppScreenshotAsync(Long appId, String appUrl){

        // 使用线程池异步执行
        new Thread(() -> {
            try {
                // 调用截图服务生成, 并上传至对象存储
                String screenshotUrl = screenshotService.generateAndUploadScreenshot(appUrl);

                // 更新应用封面字段
                App updateApp = new App();
                updateApp.setId(appId);
                updateApp.setCover(screenshotUrl);
                boolean updated = updateById(updateApp);
                if(!updated){
                    throw new BusinessException(ErrorCode.OPERATION_ERROR, "更新应用封面失败！");
                }
            } catch (Exception e) {
                log.error("异步生成应用截图失败: {}", e.getMessage(), e);
            }
        }).start();
    }
}
